package core.util.mybatis;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import core.util.PageEntity;
import core.util.WebUtil;
/**
 * 智能分页
 * @author 白俊
 * 
 *
 */
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
		RowBounds.class, ResultHandler.class }) })
public class MybatisPlugins implements Interceptor {
	Logger logger = LoggerFactory.getLogger(MybatisPlugins.class);
	public Object intercept(Invocation invocation) throws Throwable {
		// 查询对象参数[1]
		if (invocation.getArgs()[1] instanceof PageEntity) {
			PageEntity<?> condition = (PageEntity<?>) invocation.getArgs()[1];
			if (null != condition.getPage()) {
				MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
				Object paramObject = invocation.getArgs()[1];
				String json = JSON.toJSONString(paramObject);
				JSONObject paramJson = JSON.parseObject(json);
				int count = getCountTransaction(invocation, ms, paramObject, paramJson);
				condition.getPage().setCount(count);
				invocation.getArgs()[0] = handleLimit(ms, paramObject, paramJson);
			}
		}
		return invocation.proceed();
	}

	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	public void setProperties(Properties properties) {
	}

	public MappedStatement handleLimit(MappedStatement ms, Object paramObject, JSONObject paramJson)
			throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
		int pageSize = paramJson.getJSONObject("page").getIntValue("pageSize");
		int pageNo = paramJson.getJSONObject("page").getIntValue("pageNo");
		int start = (pageNo - 1) * pageSize;
		String sql = ms.getSqlSource().getBoundSql(paramObject).getSql().replaceAll(";", " ") + " limit " + start + ","
				+ pageSize;
		final BoundSql boundSql = new BoundSql(ms.getConfiguration(), sql,
				ms.getSqlSource().getBoundSql(paramObject).getParameterMappings(), paramObject);
		SqlSource sqlSource = new SqlSource() {
			@Override
			public BoundSql getBoundSql(Object parameterObject) {
				return boundSql;
			}
		};
		return copyMappedStatement(ms, sqlSource);
//
//		if (logger.isInfoEnabled()) {
//			List<ParameterMapping> mappings = ms.getSqlSource().getBoundSql(paramObject).getParameterMappings();
//			List<Object> params = new ArrayList<>(mappings.size());// 用于log
//			for (ParameterMapping key : mappings) {
//				Object property = paramJson.get(key.getProperty());
//				if (logger.isInfoEnabled()) {
//					if (property instanceof String) {
//						params.add("'" + property + "'");
//					} else if (property instanceof Date) {
//						params.add("'" + WebUtil.getDateStr((Date) property) + "'");
//					} else {
//						params.add(property);
//					}
//				}
//			}
//			String string = "[" + StringUtils
//					.replaceEach(sql, new String[] { "\n", "\t", "\\?", "  " }, new String[] { " ", " ", "%s", " " })
//					.replaceAll("\\?", "%s") + "]";
//			logger.info(String.format(string, params.toArray()));
//		}
	}
	
	public MappedStatement copyMappedStatement(MappedStatement ms, SqlSource sqlSource) {
		MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), sqlSource,
				ms.getSqlCommandType());
		builder.resource(ms.getResource());
		builder.fetchSize(ms.getFetchSize());
		builder.statementType(ms.getStatementType());
		builder.keyGenerator(ms.getKeyGenerator());
		if (ms.getKeyProperties() != null) {
			for (String keyProperty : ms.getKeyProperties()) {
				builder.keyProperty(keyProperty);
			}
		}
		builder.timeout(ms.getTimeout());
		builder.parameterMap(ms.getParameterMap());
		builder.resultMaps(ms.getResultMaps());
		builder.cache(ms.getCache());
		return builder.build();
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public int getCountTransaction(Invocation invocation, MappedStatement ms, Object paramObject, JSONObject paramJson)
			throws SQLException {
		String sql = new StringBuilder("select count(*) rowstotalcount from (")
				.append(ms.getSqlSource().getBoundSql(paramObject).getSql()).append(") bj").toString()
				.replaceAll(";", " ");
		final BoundSql boundSql = new BoundSql(ms.getConfiguration(), sql,
				ms.getSqlSource().getBoundSql(paramObject).getParameterMappings(), paramObject);
		SqlSource sqlSource = new SqlSource() {
			@Override
			public BoundSql getBoundSql(Object parameterObject) {
				return boundSql;
			}
		};
		MappedStatement tempMS = copyMappedStatement(ms, sqlSource);
		Executor d = (Executor) invocation.getTarget();
		List<Object> result = d.query(tempMS, invocation.getArgs()[1], (RowBounds) invocation.getArgs()[2],
				(ResultHandler) invocation.getArgs()[3]);
		if (WebUtil.isNotBlank(result)) {
			PageEntity<Object> page = (PageEntity<Object>) result.get(0);
			return page.getRowstotalcount();
		} else {
			return 0;
		}
	}
	
	public int getCount(MappedStatement mappedStatement, Object paramObject, JSONObject paramJson) throws SQLException {
		String json = JSON.toJSONString(paramObject);
		JSONObject param = JSON.parseObject(json);
		Connection con = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		int count = 0;
		try {
			con = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection();
			List<ParameterMapping> mappings = mappedStatement.getSqlSource().getBoundSql(paramObject)
					.getParameterMappings();
			String sql = new StringBuilder("select count(*) from (")
					.append(mappedStatement.getSqlSource().getBoundSql(paramObject).getSql()).append(") bj").toString()
					.replaceAll(";", " ");
			List<Object> params = new ArrayList<>(mappings.size());// 用于log
			ps = con.prepareStatement(sql);
			int i = 1;
			for (ParameterMapping key : mappings) {
				Object property = null;
				// 处理二级参数 user.name
				if (key.getProperty().contains(".")) {
					String[] pr = key.getProperty().split("\\.");
					property = param.getJSONObject(pr[0]).get(pr[1]);
				} else {
					property = param.get(key.getProperty());
				}
				ps.setObject(i, property);
				i++;
				if (logger.isInfoEnabled()) {
					if (property instanceof String) {
						params.add("'" + property + "'");
					} else if (property instanceof Date) {
						params.add("'" + WebUtil.getDateStr((Date) property) + "'");
					} else {
						params.add(property);
					}
				}
			}
			rs = ps.executeQuery();
			if (rs.next()) {
				count = rs.getInt(1);
			}

			if (logger.isInfoEnabled()) {
				String string = "[Count=" + count + "]["
						+ StringUtils.replaceEach(sql, new String[] { "\n", "\t", "\\?", "  " },
								new String[] { " ", " ", "%s", " " }).replaceAll("\\?", "%s")
						+ "]";
				logger.info(String.format(string, params.toArray()));
			}

		} finally {
			if (rs != null) {
				rs.close();
			}
			if (ps != null) {

				ps.close();
			}
			if (con != null) {

				con.close();
			}
		}
		return count;
	}

	public void printSqlLog(Invocation invocation) {
		// MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
		// Object parameter = invocation.getArgs()[1];
		// String sql =
		// mappedStatement.getSqlSource().getBoundSql(parameter).getSql().replaceAll("\n",
		// "")
		// .replaceAll("\t", " ").replaceAll(" ", " ").replaceAll("\\?", "%s");
		// @SuppressWarnings("unchecked")
		// ParamMap<Object> param = (ParamMap<Object>)
		// mappedStatement.getSqlSource().getBoundSql(parameter)
		// .getParameterObject();
		// List<ParameterMapping> keys =
		// mappedStatement.getSqlSource().getBoundSql(parameter).getParameterMappings();
		// List<Object> params = new ArrayList<>();
		// for (ParameterMapping key : keys) {
		// Object property = param.get(key.getProperty());
		// if (property instanceof String) {
		// property = "'" + property + "'";
		// }
		// if (property instanceof Date) {
		// property = "'" + WebUtil.getDateStr((Date) property) + "'";
		// }
		// params.add(property);
		// }
		// String position = mappedStatement.getId();
		// String[] positions = position.split("\\.");
		// if (positions.length > 2) {
		// position = positions[positions.length - 2] + "." + positions[positions.length
		// - 1];
		// }
		// logger.info(position + ":" + String.format(sql, params.toArray()));
	}
}
